package mspbots.autotask.client.core.app;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.autotask.client.core.AbstractToolsUtil;
import mspbots.autotask.client.core.BaseSyncService;
import mspbots.autotask.client.core.client.ClientProperties;
import mspbots.autotask.common.QueryBody;
import mspbots.autotask.common.RedisKeys;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

/**
 * mspbots.core.autotask.core.app.AutoTaskService
 *
 * @author <a href="https://github.com/vnobo">Alex bob</a>
 * @date Created by 2020/7/15
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class AutoTaskServiceImpl extends AbstractToolsUtil {

    private final mspbots.autotask.client.core.data.users.UsersServiceImpl usersService;
    private final Map<String, BaseSyncService> syncServiceMap;

    private Mono<String> getBaseUrl(String email) {
        return super.autoTaskClient.get(UriComponentsBuilder.newInstance()
                .scheme("https").host("webservices.autotask.net")
                .path("/atservicesrest/v1.0/zoneInformation")
                .queryParam("user", email).build().toUri())
                .retry(3)
                .map(result -> result.get("url").textValue());
    }

    public Mono<Void> delete(Long tenantId) {
        return this.reactiveRedisTemplate.opsForValue()
                .delete(RedisKeys.TENANT_PROPERTIES_REDIS_KEY_PREFIX + tenantId)
                .then();
    }

    public Mono<Boolean> initialize(ClientProperties properties) {
        return this.getBaseUrl(properties.getUserName())
                .contextWrite(ctx -> ctx.put("properties", properties))
                .flatMap(url -> {
                    properties.setBaseUrl(url);
                    return this.usersService.countUsers(properties, QueryBody.withDefault())
                            .map(jsonNode -> jsonNode.get("queryCount").intValue())
                            .map(x -> x > 0)
                            .flatMap(c -> super.reactiveRedisTemplate.opsForValue()
                                    .set(RedisKeys.TENANT_PROPERTIES_REDIS_KEY_PREFIX + properties.getTenantId(),
                                            properties, Duration.ofDays(36500)));
                })
                .doOnSuccess(res -> Flux.fromStream(this.syncServiceMap.values().parallelStream())
                        .flatMap(v -> Flux.concatDelayError(
                                v.syncInformation(properties),
                                v.syncFast(properties),
                                v.syncSlow(properties))
                        ).subscribe());
    }

    public Mono<Boolean> onOffTenant(Long tenantId, String onOff) {
        if ("ON".equalsIgnoreCase(onOff)) {
            return this.stringRedisTemplate.opsForValue()
                    .set(RedisKeys.ON_OFF_TENANT_TASK_REDIS_KEY + tenantId, onOff.toUpperCase(),
                            Duration.ofDays(365));
        } else {
            return this.stringRedisTemplate.opsForValue()
                    .delete(RedisKeys.ON_OFF_TENANT_TASK_REDIS_KEY + tenantId);
        }
    }

    public Flux<Map<String, Boolean>> synchronise(Long tenantId) {
        var syncFlux = this.reactiveRedisTemplate.opsForValue()
                .get(RedisKeys.TENANT_PROPERTIES_REDIS_KEY_PREFIX + tenantId)
                .map(data -> this.objectMapper.convertValue(data, ClientProperties.class))
                .flatMapMany(clientProperties -> Flux.fromStream(this.syncServiceMap.values().parallelStream())
                        .flatMap(v -> Flux.concatDelayError(v.syncInformation(clientProperties), v.syncFast(clientProperties)
                                , v.syncSlow(clientProperties))));

        return this.stringRedisTemplate.scan(ScanOptions.scanOptions()
                .match(RedisKeys.TIME_ANCHOR_REDIS_KEY_PREFIX + "*:" + tenantId).count(100).build())
                .flatMap(key -> this.stringRedisTemplate.opsForValue()
                        .set(key, LocalDateTime.now().minusYears(3)
                                .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), Duration.ofDays(7))
                        .map(result -> Map.of(key, result)))
                .doOnTerminate(syncFlux::subscribe);
    }
}
